//@author A0081058H
package net.mysoc.w111j.ui.expensedetail;

import java.util.ArrayList;
import java.util.regex.Pattern;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Pair;
import net.mysoc.w111j.model.Category;
import net.mysoc.w111j.model.PaymentType;
import net.mysoc.w111j.ui.validation.Validation;

import com.sun.javafx.scene.control.behavior.TextAreaBehavior;
import com.sun.javafx.scene.control.skin.SkinBase;

/**
 * The view for the expense detail dialog window.
 */
public class ExpenseDetailView extends BorderPane {
    //private static Logger logger = LoggerFactory.getLogger(MainPresenter.class);

    private final ExpenseDetailModel model;
    private ExpenseDetailPresenter presenter;

    /**
     * Constructor. Builds the view and links the {@code ExpenseDetailModel} linked to this view.
     *
     * @param model
     *        the {@code ExpenseDetailModel} linked to this view.
     */
    public ExpenseDetailView(ExpenseDetailModel model) {
        this.model = model;
        buildView();
    }

    /**
     * Sets the {@code ExpenseDetailPresenter} linked to this view.
     *
     * @param presenter
     *        the {@code ExpenseDetailPresenter} linked to this view.
     */
    public void setPresenter(ExpenseDetailPresenter presenter) {
        this.presenter = presenter;
    }

    /**
     * Builds the view.
     */
    public void buildView() {
        setTop(addTitle());
        setCenter(addExpenseDetailView());
    }

    private HBox addTitle() {
        Text title = new Text("Expense details");
        title.getStyleClass().addAll("typo", "page-header");

        return HBoxBuilder.create().children(title).styleClass("expense-detail-view-title").build();
    }

    /**
     * Creates a {@code VBox} for expense details, links with {@code ExpenseDetailPresenter}, and returns the expense
     * detail view.
     *
     * @return the expense detail view.
     */
    private VBox addExpenseDetailView() {
        VBox gridpane = new VBox();

        HBox windowControl = HBoxBuilder.create().children(addOKButton(), addCancelButton())
                .styleClass("window-control").build();

        ArrayList<Pair<Label, Node>> forms = new ArrayList<>();
        forms.add(new Pair<Label, Node>(new Label("Name"), addNameField()));
        forms.add(new Pair<Label, Node>(new Label("Price"), addPriceField()));
        forms.add(new Pair<Label, Node>(new Label("Date"), addDateField()));
        forms.add(new Pair<Label, Node>(new Label("Category"), addCategoryChoicebox()));
        forms.add(new Pair<Label, Node>(new Label("Details"), addDetailsTextarea()));
        forms.add(new Pair<Label, Node>(new Label("Payment type"), addPaymentTypeChoicebox()));

        int i;
        for (i = 0; i < forms.size(); i++) {
            VBox labelledField = new VBox();
            Pair<Label, Node> form = forms.get(i);
            Label label = form.getKey();
            Node field = form.getValue();
            label.setLabelFor(field);
            labelledField.getChildren().addAll(label, field);
            label.getStyleClass().addAll("typo", "form-label");
            labelledField.getStyleClass().add("labelled-field");
            labelledField.setMinHeight(40);
            gridpane.getChildren().addAll(labelledField);
        }

        gridpane.getStyleClass().add("expense-detail-view");
        gridpane.getChildren().add(windowControl);

        return gridpane;
    }

    /**
     * Creates the name {@code TextField} for this dialog. Bound to the name property in {@code ExpenseDetailModel}.
     *
     * @return a bound name {@code TextField}.
     */
    private HBox addNameField() {
        TextField field = new TextField();
        field.textProperty().bindBidirectional(model.nameProperty());
        field.setPromptText("Name");

        final Label nameValidationLabel = new Label();

        ChangeListener<Object> validateName = new ChangeListener<Object>() {
            @Override
            public void changed(ObservableValue<?> o, Object oldVal, Object newVal) {
                Validation.formatValidationLabel(presenter.validateName(), nameValidationLabel);
            }
        };

        field.textProperty().addListener(validateName);

        return HBoxBuilder.create().children(field, nameValidationLabel).styleClass("expense-detail-view-name").build();
    }

    /**
     * Creates the price {@code TextField} for this dialog. Bound to the price property in {@code ExpenseDetailModel}.
     *
     * @return a bound price {@code TextField}.
     */
    private HBox addPriceField() {
        TextField price = new TextField();
        price.setPromptText("0.00");
        price.textProperty().bindBidirectional(model.priceProperty());

        // Consume characters other than digits and full stop.
        price.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
            private final Pattern decimalPattern = Pattern.compile("[\\d\\.]");

            @Override
            public void handle(KeyEvent event) {
                if (!decimalPattern.matcher(event.getCharacter()).matches()) {
                    event.consume();
                }
            }
        });

        final Label priceValidationLabel = new Label();

        ChangeListener<Object> validatePrice = new ChangeListener<Object>() {
            @Override
            public void changed(ObservableValue<?> o, Object oldVal, Object newVal) {
                Validation.formatValidationLabel(presenter.validatePrice(), priceValidationLabel);
            }
        };

        price.textProperty().addListener(validatePrice);

        return HBoxBuilder.create().children(price, priceValidationLabel).styleClass("expense-detail-view-price")
                .build();
    }

    /**
     * Creates the date {@code TextField} for this dialog. Bound to the date property in {@code ExpenseDetailModel}.
     *
     * @return a bound date {@code TextField}.
     */
    private HBox addDateField() {
        // There is no date picker in JavaFX 2.2, although it is planned for
        // Java 8. For now we stick to a simple text field. Also consider
        // changing this to multiple comboboxes (DMY).
        HBox hbox = new HBox();
        TextField field = new TextField();
        final Label dateValidationLabel = new Label();

        ChangeListener<Object> validateDate = new ChangeListener<Object>() {
            @Override
            public void changed(ObservableValue<?> o, Object oldVal, Object newVal) {
                Validation.formatValidationLabel(presenter.validateDate(), dateValidationLabel);
            }
        };

        field.textProperty().bindBidirectional(model.dateProperty());
        field.textProperty().addListener(validateDate);
        field.setPromptText("4 April 1984");

        hbox.getStyleClass().addAll("expense-detail-view-date");
        hbox.getChildren().addAll(field, dateValidationLabel);

        return hbox;
    }

    /**
     * Creates the category {@code ComboBox} for this dialog. Items are populated from available categories in
     * {@code ExpenseService}. Bound to the category property in {@code ExpenseDetailModel}.
     *
     * @return a bound category {@code ComboBox}.
     */
    private HBox addCategoryChoicebox() {
        ComboBox<Category> choicebox = new ComboBox<>(model.categoriesListProperty());

        if (this.model.getCategory() == null) {
            choicebox.getSelectionModel().select(0);
        } else {
            choicebox.getSelectionModel().select(model.getCategory());
        }

        model.categoryProperty().bindBidirectional(choicebox.valueProperty());

        choicebox.setPrefWidth(250);
        choicebox.setMaxWidth(500);

        return HBoxBuilder.create().children(choicebox).styleClass("expense-detail-view-category").build();
    }

    /**
     * Creates the details {@code TextArea} for tihs dialog. Binds to the details property in {@code ExpenseDetailModel}
     * .
     *
     * @return a bound details {@code TextArea}.
     */
    private HBox addDetailsTextarea() {
        final TextArea textarea = new TextArea();
        textarea.textProperty().bindBidirectional(model.detailsProperty());
        textarea.setPrefRowCount(4);
        textarea.setPromptText("Details");

        // Stop details text area from capturing focus (not traversing fields on
        // TAB)
        textarea.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                if (event.getCode() == KeyCode.TAB) {
                    SkinBase skin = (SkinBase) textarea.getSkin();
                    if (skin.getBehavior() instanceof TextAreaBehavior) {
                        TextAreaBehavior behavior = (TextAreaBehavior) skin.getBehavior();
                        if (event.isControlDown()) {
                            behavior.callAction("InsertTab");
                        } else if (event.isShiftDown()) {
                            behavior.callAction("TraversePrevious");
                        } else {
                            behavior.callAction("TraverseNext");
                        }
                        event.consume();
                    }
                }
            }
        });

        return HBoxBuilder.create().children(textarea).styleClass("expense-detail-view-details").build();
    }

    /**
     * Creates the payment type {@code ComboBox} for this dialog. Binds to the paymentType property in
     * {@code ExpenseDetailModel}.
     *
     * @return a bound payment type {@code ComboBox}
     */
    private HBox addPaymentTypeChoicebox() {
        ComboBox<PaymentType> choicebox = new ComboBox<>(model.paymentTypesListProperty());
        model.paymentTypeProperty().bindBidirectional(choicebox.valueProperty());
        choicebox.getSelectionModel().select(0); // Defaults to first item

        return HBoxBuilder.create().children(choicebox).styleClass("expense-detail-view-paymenttype").build();
    }

    /**
     * Creates the OK {@code Button} for this dialog. This calls the presenter to read input from the
     * {@code ExpenseDetailModel}
     *
     * @return an OK {@code Button}
     */
    private Button addOKButton() {
        Button button = new Button("OK");
        button.setDefaultButton(true); // Handles VK_ENTER

        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                // Closes the dialog window from inside it
                // TODO: handle case when there this is not in a dialog window

                if (presenter.validateInput()) {
                    Node source = (Node) event.getSource();
                    Stage stage = (Stage) source.getScene().getWindow();
                    stage.close();
                    presenter.handleOK();
                }
            }
        });

        button.getStyleClass().addAll("button-ok", "expense-detail-view-buttonok");
        return button;
    }

    /**
     * Creates the Cancel {@code Button} for this dialog. This calls the presenter to clear all input from the
     * {@code ExpenseDetailModel}
     *
     * @return a cancel {@code Button}
     */
    private Button addCancelButton() {
        final Button button = new Button("Cancel");
        button.setCancelButton(true); // Handles VK_ESC as well.

        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                // Closes the dialog window from inside it
                // TODO: handle case when there this is not in a dialog window
                Node source = (Node) event.getSource();
                Stage stage = (Stage) source.getScene().getWindow();
                stage.close();
            }
        });

        button.getStyleClass().addAll("button-cancel", "expense-detail-view-buttoncancel");

        return button;
    }
}
